/*
*    kulma - Kul med matematik.
*    Copyright (C) 2024  Marcus Pedersén marcus@marcux.org
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


package main

import (
	"os"
	"fmt"
	"errors"
	"strings"
	"strconv"
	"math/rand"
	"time"
	au "github.com/logrusorgru/aurora/v4"
)


// Struct represent
// a multiplication
// table
type Mul struct {
	table int
	current int
	result map[int]bool
}

// Returns random number
// from multiplication
// table between 1-10
// that has not been
// used yet.
func (m Mul) GetNumber() (int, int) {
	for {
		timeNow := time.Now().UnixNano()
		r := rand.New(rand.NewSource(timeNow))
		rInt := r.Intn(10) + 1

		if len(m.result) >= 10 {
			return 11, 11
		}
		
		if _, ok := m.result[rInt]; ! ok {
			return rInt, m.table
		}
	}
}


// Returns the next
// random number in
// a pritty print format
// string: "a x b ="
// Bool specifying if
// there is a next or
// all numbers in series
// has been used.
func (m *Mul) next() (string, bool) {
	a, b := m.GetNumber()
	m.current = a
	p := fmt.Sprintf("%d x %d = ", a, b)

	if len(m.result) >= 10 {
		return p, false
	} else {
		return p, true
	}
}


// Updates internal
// referense and returns
// bool if correct or not
func (m *Mul) correct(a int) bool {
	res := false
	if (m.current * m.table) == a {
		m.result[m.current] = true
		res = true
	} else {
		m.result[m.current] = false
	}
	
	m.current = 0
	return res
}


// Returns a prity print
// of total score if all
// results otherwise
// empty string.
func (m Mul) summary() string {
	if len(m.result) == 10 {
		noCorr := 0
		for _, r := range m.result {
			if r {
				noCorr += 1
			}
		}

		resStr := fmt.Sprintf("Du hade %d rätt av 10!!\nBra jobbat!!", noCorr)
		return resStr
	}

	return ""
}



// Create a new table
// Argument must be between
// one and ten (1-10)
func NewMul(tableNo int) (Mul, error) {
	if tableNo >= 1 && tableNo <= 10 {
		return Mul{
			table: tableNo,
			result: make(map[int]bool),
			},
			nil
	} else {
		return Mul{
			table: 0,
			result: make(map[int]bool),
			},
			errors.New("Table needs to be between one and ten (1-10)") 
	}
}

// Print the initial
// text when the program
// is started.
func headerText() {
	fmt.Printf("%s\n",
		au.Bold(au.Yellow("*************************")))
	fmt.Printf("%s %s %s\n",
		au.Bold(au.Yellow("*")),
		au.Bold(au.Green("Välkommen till kulma!")),
		au.Bold(au.Yellow("*")))
	fmt.Printf("%s\n",
		au.Bold(au.Yellow("*************************")))
	fmt.Println("")
}



// Get users name
func getName() string {
	var name string
	
	fmt.Println("Vad heter du?")
	for {
		if _, err := fmt.Scanln(&name); err != nil {
			fmt.Println("Namnet kan inte vara tomt, försök igen.")
		} else {

			name = strings.TrimSpace(name)
			break
		}
	}
		
	return name
}


// Prints header
// for multiplication
func mulText() {
	fmt.Println("Dags att köra...")
	fmt.Printf("%s\n",
		au.Yellow("*******************"))
	fmt.Printf("%s %s %s\n",
		au.Yellow("*"),
		au.Green("Multiplikation!"),
		au.Yellow("*"))
	fmt.Printf("%s\n",
		au.Yellow("*******************"))
	fmt.Println("")
	
}


// Ask user for multiplication
// table, returns Mul
func getMulTable() Mul {
	var table string

	fmt.Printf("\nVälj multiplikationstabell (1-10):\n")
	for {
		if _, err := fmt.Scanln(&table); err != nil {
			fmt.Println("Tabellen måste vara ett tal mellan 1 och 10.")
			fmt.Println("Försök igen.")
			continue
		} else {
			if i, err := strconv.Atoi(table); err != nil {
				fmt.Println("Tabellen måste vara ett tal mellan 1 och 10.")
				fmt.Println("Försök igen.")
				continue
			} else {
				if i >= 1 && i <= 10 {
					m, _ := NewMul(i)
					return m
				} else {
					fmt.Println("Tabellen måste vara ett tal mellan 1 och 10.")
					fmt.Println("Försök igen.")
					continue
				}
			}
		} 
	}
}

func main() { 
	headerText()
	name := getName()
	fmt.Printf("\nHej, %s!\n\n", name)
	mulText()
	mul := getMulTable() 

	timeStart := time.Now()

	var strAnswer string
	var corrAnswer bool
	for p, n := mul.next(); n == true; p, n = mul.next() {
		fmt.Print(p)
		if _, err := fmt.Scanln(&strAnswer); err != nil {
			corrAnswer = mul.correct(-1)
		} else {
			if a, err := strconv.Atoi(strAnswer); err == nil {
				corrAnswer = mul.correct(a)
			} else {
				corrAnswer = mul.correct(-1)
			}
		}
		if corrAnswer {
			fmt.Printf("%s\n\n",
				au.Green("Rätt svar!"))
		} else {
			fmt.Printf("%s\n\n",
				au.Red("Fel svar!"))
		}
	}

	timeUsed := time.Now().Sub(timeStart)

	timeText := fmt.Sprintf("Du gjorde tio uppgifter i\nmultiplikationstabell %d\npå tiden: %s", mul.table, timeUsed)
	fmt.Println(au.Bold(au.Green(timeText)))
	fmt.Println(au.Bold(au.Green(mul.summary())))
}
